#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#include "mcl/da.h"
#include "mcl/code.h"
#include "mcl/val.h"
#include "mcl/log.h"
#include "mcl/rt.h"
#include "mcl/writer.h"
#include "mcl/config.h"
#include "mcl/parser.h"
#include "mcl/builtin.h"

void delete_val(struct lvalue *v)
{
	(void)v;
	// TODO
}

void gcmark(struct runtime *rt);
void gcmark_value(struct runtime *rt, struct lvalue *v);

void gcmark_func(struct runtime *rt, struct lambda *func)
{
	// TODO mark parent envs.
	for (usize i = 0; i < func->env->binds.len; ++i) {
		gcmark_value(rt, func->env->binds.data[i].val);
	}
}

void gcmark_value(struct runtime *rt, struct lvalue *v)
{
	(void)rt;
	(void)v;
}

void gcmark(struct runtime *rt)
{
	struct da_valptr *l = &rt->living[rt->currl];

	for (usize i = 0; i < l->len; ++i) {
		gcmark_value(rt, l->data[i]);
	}
}

void gcsweep(struct runtime *rt)
{
	(void)rt;
}

void gc(struct runtime *rt)
{
	gcmark(rt);
	gcsweep(rt);
}

#if IS_ON(CONFIG_TEST)
void test_parse_comment(void);
void test_parse(void);

static void test(void)
{
	loginfo("start test");
	loginfo("sizeof of val: %zu", sizeof(struct val));

	test_parse();
	test_parse_comment();

	loginfo("test ok");
}
#endif

static void
print_pos(struct writer *w, char const *code, struct pos pos, usize around)
{
	struct pos pos1 = {.row = 1, .col = 1};

	for (usize i = 0; code[i]; ++i) {
		if (pos.row - around <= pos1.row &&
		    pos1.row <= pos.row + around) {
			if (code[i] == '\n') {
				write_cstr(w, "\n");
				write_i64(w, pos1.row);
				write_cstr(w, " | ");
			} else {
				write_bytes(w, code + i, 1);
			}
		} else if (pos.row + around < pos1.row) {
			break;
		}

		if (code[i] == '\n') {
			pos1.row += 1;
			pos1.col = 1;
		} else {
			pos1.col += 1;
		}
	}

	write_cstr(w, "\n");
}

int main(void)
{
	set_logging_level_filter(LOG_INFO);

#if IS_ON(CONFIG_TEST)
	test();
#endif
	int return_code;

	FILE *fp = fopen("input.mcl", "r");
	if (fp == NULL) {
		logerror("failed to open input.mcl");
		return_code = CODE_RUNTIME;
		goto finish;
	}

	fseek(fp, 0, SEEK_END);
	long len = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	char *source_code = malloc(len + 1);
	fread(source_code, len, 1, fp);
	source_code[len] = 0;

	struct runtime rt = RUNTIME_INIT;
	rt_add_builtin(&rt.rootenv);

	struct parser p = {.rt = &rt, .code = source_code, .curr = 0};
	struct writer w = new_writer_from_file(stdout);

	struct lvalue_uninit val_parse;
	struct lvalue *val_eval;

	while (!eof(&p)) {
		struct lvalue_opt val_opt = parse_val(&p, &val_parse);

		if (val_opt.code != CODE_OK) {
			logerror(
				"syntax error, row %d, col %d",
				(int)p.pos.row,
				(int)p.pos.col);

			struct writer w = new_writer_from_file(stderr);
			print_pos(&w, p.code, p.pos, 1);

			return_code = CODE_SYNTAX;
			goto finish;
		}

		val_eval = eval(&rt, &rt.rootenv, val_opt.val);
		if (val_eval == NULL) {
			if (rt.panic) {
				logerror(
					"panic: (%d, %d)",
					(int)val_eval->pos.row,
					(int)val_eval->pos.col);

				return_code = CODE_PANIC;
				goto finish;
			}

			loginfo("runtime error, row %d, col %d",
				(int)val_opt.val->pos.row,
				(int)val_opt.val->pos.col);

			// struct writer w = new_writer_from_file(stderr);

			// loginfo("begin call stack");
			// for (usize i = 0; i < rt.func_stack.len; ++i) {
			// 	struct pos pos = rt.func_stack.data[i];
			// 	print_pos(&w, p.code, pos, 0);
			// }
			// loginfo("end call stack");

			return_code = CODE_RUNTIME;
			goto finish;
		}

		write_cstr(&w, "=> ");
		write_val(&w, 0, val_eval);
		write_cstr(&w, "\n");
	}

	return_code = CODE_OK;
finish:
	if (fp != NULL) {
		fclose(fp);
	}
	return return_code;
}
